/**
 * @projectDescription X-Cube: Ajax Library
 * @author Julio Greff
 * @version 0.2
 */

/**
 * XCube Class
 * Contains the main methods to manipulate Ajax requests
 * @param {String} url URL to be requested
 * @param {Object} options Optional, object with options
 * @return {Object} X-Cube object
 */
var XCube = function(url, options) {
	return this.initialize(url, options)
}

XCube.prototype.initialize = function(url, options) {
	this.options = {
		"method": "get",
		"data": "",
		"autoCancel": true,
		"async": true,
		"onComplete": function(){},
		"onRequest": function(){},
		"onCancel": function(){},
		"onFailure": function(){},
		"cache": false,
		"localCache": false,
		"history": false,
		"changeLinks": false,
		"update": false
	}
	this.setXmlHttp().setOptions(options)
	this.url = url
	if((this.options.method = this.options.method.toLowerCase()) == "post")
		this.setHeader("Content-Type", "application/x-www-form-urlencoded")
	if(this.options.cache == false) {
		this.setHeader("Pragma", "no-cache")
		this.setHeader("Cache-Control", "no-cache")
	}
	if(this.options.history == true) {
		if(!History) History = new History()
	}
	if(this.options.changeLinks == true) {
		this.changeUrls()
	}
	return this;
}

XCube.prototype.queue = []

XCube.prototype.headers = {
	"X-Requested-With": "XMLHttpRequest"
}

XCube.prototype.localCache = {}

/**
 * Set headers to the requests
 * @param {String} headerName Header Name
 * @param {String} headerValue Header Value
 */
XCube.prototype.setHeader = function(name, value) {
	this.headers[name] = value
	return this
}

XCube.prototype.setOptions = function(options) {
	for(var option in options) {
		this.options[option] = options[option]
	}
	return this
}

XCube.prototype.setXmlHttp = function() {
	this.XmlHttp = (window.XMLHttpRequest) ? new XMLHttpRequest() : (ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : false)
	return this
}

XCube.prototype.onStateChange = function(data) {
	if(this.XmlHttp.readyState == 4) {
		if(this.XmlHttp.status == 200) {
			if(this.options.localCache == true) {
				this.localCache[escape(data)] = {
					"responseText": this.XmlHttp.responseText,
					"responseXML": this.XmlHttp.responseXML
				}
			}
			if(this.options.update != false && document.getElementById(this.options.update)) document.getElementById(this.options.update).innerHTML = this.XmlHttp.responseText
			this.options.onComplete(this.XmlHttp.responseText, this.XmlHttp.responseXML)
			this.running = false
			setTimeout(this.executeQueue.bind(this), 100)
		}
		else
			this.options.onFailure(this.XmlHttp.status, this.XmlHttp.statusText)
	}
	return this
}

XCube.prototype.executeQueue = function() {
	if(this.queue.length > 0)
		this.request(this.queue.shift())
	return this
}

/**
 * Starts a request
 * @param {String} data Data to be sent
 */
XCube.prototype.request = function(data) {
	if(this.options.autoCancel == true)
		this.stop()
	else if(this.running == true) {
		this.queue.push(data)
		return this
	}
	var url = this.url
	data = data || this.options.data || null
	if(this.options.method == "get" && data) {
		url = this.url + ((this.url.indexOf("?") < 0) ? "?" : "&" ) + data
		data = null
	}
	this.running = true
	if(this.options.localCache == true && this.localCache[escape(data || url)]) {
		this.options.onComplete(this.localCache[escape(data || url)].responseText, this.localCache[escape(data || url)].responseXML)
		this.running = false
		this.executeQueue()
		return this
	}
	this.XmlHttp.open(this.options.method, url, this.options.async)
	this.XmlHttp.onreadystatechange = this.onStateChange.bind(this, data || url)
	for(var header in this.headers) this.XmlHttp.setRequestHeader(header, this.headers[header])
	this.options.onRequest()
	this.XmlHttp.send(data)
	return this
}

/**
 * Stops a running request
 */
XCube.prototype.stop = function() {
	if(this.running == false)
		return this
	this.XmlHttp.abort()
	this.running = false
	this.options.onCancel()
	this.XmlHttp.onreadystatechange = function(){}
	this.setXmlHttp()
	return this
}

/**
 * This is where the magic starts.
 * Grabs all the a elements in which rel attribute equals 'async', 
 * and then modifies these links to make them call Ajax requests
 * @param {Object} parentElement Parent element container
 */
XCube.prototype.changeUrls = function(parentElement) {
	var links = (parentElement || document).getElementsByTagName("a")
	for(var i = 0, link; link = links[i]; i++) {
		var url = link.getAttribute("href")
		if(url.indexOf(this.url) >= 0 && link.getAttribute("rel") == "async") {
			if(this.options.history == true) {
				link.setAttribute("href", "#" + url.substring(this.url.length + 1))
			}
			else {
				var request = this.request.bind(this)
				var self = this
				link.onclick = function() {
					var data = this.getAttribute("href").substring(self.url.length + 1)
					request(data)
					return false
				}
			}
		}
	}
}

/**
 * Makes a simple Ajax request
 * @param {String} url URL to request
 * @param {String} update Element to be updated by the request
 * @param {Object} options Options to set
 */
XCube.simpleRequest = function(url, update, options) {
	var options = (options || {})
	options.update = update
	options.changeLinks = false
	var request = new XCube(url, options)
	request.request()
}

/**
 * History Class
 * Controls the history of the requests
 */
var History = function() {
	return this.initialize()
}

History.prototype.initialize = function() {
	this.appHash = ""
	if(this.isIe = navigator.userAgent.toLowerCase().indexOf("msie") > -1)
		this.createIframe()
	this.checkInterval = setInterval(this.handleHistory.bind(this), 100)
	return this
}

/**
 * Creates an iFrame (IE sucks :D)
 */
History.prototype.createIframe = function() {
	if(this.isIe == false) return this
	var iFrame = document.createElement("iframe")
	iFrame.setAttribute("src", "control.htm?id=" + window.location.hash.substring(1))
	iFrame.setAttribute("id", "control-iframe")
	iFrame.style.display = "none"
	document.body.appendChild(iFrame)
	return this
}

/**
 * Makes the initial bookmark check
 */
History.prototype.checkBookmark = function() {
	return window.location.hash = ((browserHash = this.getHash()) == "") ? this.appDefault : window.location.hash
}

/**
 * Verify history changes
 */
History.prototype.handleHistory = function() {
	if((browserHash = this.getHash()) != this.appHash) {
		if(browserHash != "") {
			this.appHash = browserHash
			this.makeHistory(browserHash)
		}
	}
	return this
}

/**
 * Creates a new history entry
 * @param {String} newHash Hash of the new entry
 * @param {String} fnId ID of the function
 */
History.prototype.makeHistory = function(newHash) {
	if(this.isIe == true)
		document.getElementById("control-iframe").setAttribute("src", "control.htm?id=" + newHash)
	else
		window.location.hash = newHash
	return this
}

/**
 * Gets the browser hash
 * @return {String} Hash
 */
History.prototype.getHash = function() {
	console.log(window.location.href.split("#")[1])
	return (hash = window.location.href.split("#")[1]) ? hash : ""
}

/**
 * Binds a function to a determined scope
 * @param {Function} scope Scope
 * @param {Object} arguments Arguments
 * @return {Function} Function bind
 */
Function.prototype.bind = function() {
	var self = this, args = $A(arguments), scope = args.shift()
	return function() {
		return self.apply(scope, args.concat($A(arguments)))
	}
}

/**
 * Transforms enumerable objects into arrays
 * @param {Object} object Object to be transformed
 * @return {Array} Array
 */
var $A = function(object) {
	var array = []
	for(var i = 0; i < object.length; i++)
		array[i] = object[i]
	return array
}